package com.tqz.rc.server.core;

import com.tqz.rc.api.Cluster;
import com.tqz.rc.api.Instance;
import com.tqz.rc.common.constant.DefaultValueConstant;
import com.tqz.rc.common.constant.InstanceActionConstant;
import com.tqz.rc.common.exception.RcException;
import com.tqz.rc.common.util.JacksonUtils;
import com.tqz.rc.common.util.NamingUtils;
import com.tqz.rc.common.util.StringUtils;
import com.tqz.rc.server.consistency.ConsistencyService;
import com.tqz.rc.server.consistency.Datum;
import com.tqz.rc.server.consistency.KeyBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * <p>服务管理类。
 *
 * @author tianqingzhao
 * @since 2021/8/26 14:04
 */
@Component
public class ServiceManager {

    @Autowired
    private ConsistencyService consistencyService;

    /**
     * 真正的注册表，外层的key为：namespaceId，value为：多个环境的集合。例如：namespaceId有dev、test、prod等多个环境。
     * 里面的map的key为：服务名称，value为：服务，每个服务又会有多个实例。例如：服务名称是order-service，该服务可能还会是集群，每个集群下面又有多个实例。
     */
    private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();

    public void registerInstance(String namespaceId, String serviceName, Instance instance) throws RcException {
        createEmptyService(namespaceId, serviceName, instance.isEphemeral());
        Service service = getService(namespaceId, serviceName);

        checkServiceIsNull(service, namespaceId, serviceName);

        addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
    }

    private void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips) throws RcException {
        String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);

        Service service = getService(namespaceId, serviceName);

        synchronized (service) {
            List<Instance> instanceList = addIpAddress(service, ephemeral, ips);

            Instances instances = new Instances();
            instances.setInstanceList(instanceList);

            // 放入到instance集合中
            consistencyService.put(key, instances);
        }
    }

    /**
     * 添加实例到实例集合中。
     *
     * @param service   当前服务
     * @param ephemeral 是否是临时节点，<code>true</code>为临时节点，<code>false</code>为永久节点。
     * @param ips       最新的实例节点，可以是多个
     * @return
     */
    private List<Instance> addIpAddress(Service service, boolean ephemeral, Instance... ips) throws RcException {
        return updateIpAddresses(service, InstanceActionConstant.UPDATE_INSTANCE_ACTION_ADD, ephemeral, ips);
    }

    /**
     * 更改服务下面的实例
     *
     * @param service   当前服务
     * @param action    操作类型， {@link InstanceActionConstant}
     * @param ephemeral 是否是临时节点， <code>true</code>为临时节点，<code>false</code>为永久节点。
     * @param ips       实例列表，接收数组类型的。
     * @return
     */
    private List<Instance> updateIpAddresses(Service service, String action, boolean ephemeral, Instance... ips) throws RcException {
        Datum datum = consistencyService.get(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getServiceName(), ephemeral));

        List<Instance> currentIps = service.allIps(ephemeral);
        Map<String, Instance> currentInstances = new HashMap<>(currentIps.size());
        Set<String> currentInstanceIds = new LinkedHashSet<>();

        for (Instance instance : currentIps) {
            currentInstances.put(instance.toIpAddr(), instance);
            currentInstanceIds.add(instance.getInstanceId());
        }

        Map<String, Instance> instanceMap;
        // 如果实例集合的key之前存在，就拿出来之前的实例集合，与当前实例集合对比，如果一样就设置当前的实例集合的健康状态以及心跳时间
        if (null != datum && null != datum.value) {
            instanceMap = setValid(((Instances) datum.value).getInstanceList(), currentInstances);
        } else {
            instanceMap = new HashMap<>(ips.length);
        }

        for (Instance instance : ips) {
            // 集群名称为空，给一个默认的
            if (StringUtils.isBlank(instance.getClusterName())) {
                instance.setClusterName(DefaultValueConstant.DEFAULT_CLUSTER_NAME);
            }

            if (!service.getClusterMap().containsKey(instance.getClusterName())) {
                Cluster cluster = new Cluster(instance.getClusterName());
                cluster.init();
                service.getClusterMap().put(instance.getClusterName(), cluster);

                System.out.println("cluster: " + instance.getClusterName() + " not found," +
                        " ip: " + instance.toJson() + ", will create new cluster with default configuration.");
            }

            if (InstanceActionConstant.UPDATE_INSTANCE_ACTION_REMOVE.equals(action)) {
                instanceMap.remove(instance.getDatumKey());
            } else {
                Instance oldInstance = instanceMap.get(instance.getDatumKey());
                if (oldInstance != null) {
                    instance.setInstanceId(oldInstance.getInstanceId());
                } else {
                    instance.setInstanceId(instance.generateInstanceId(currentInstanceIds));
                }
                instanceMap.put(instance.getDatumKey(), instance);
            }
        }

        if (instanceMap.size() <= 0 && InstanceActionConstant.UPDATE_INSTANCE_ACTION_ADD.equals(action)) {
            throw new RcException("ip list can not be empty, service: " + service.getServiceName() + " , ip list:"
                    + JacksonUtils.toJson(instanceMap.values()));
        }

        return new ArrayList<>(instanceMap.values());
    }

    /**
     * 根据新实例集合的健康状态以及心跳时间设置到老的实例集合里面去，
     * 该情况是某一个服务中有这个实例集合。
     *
     * @param oldInstances     旧的实例集合
     * @param currentInstances 当前实例集合
     * @return
     */
    private Map<String, Instance> setValid(List<Instance> oldInstances, Map<String, Instance> currentInstances) {
        Map<String, Instance> instanceMap = new HashMap<>(oldInstances.size());
        for (Instance instance : oldInstances) {
            Instance instance1 = currentInstances.get(instance.toIpAddr());
            if (instance1 != null) {
                instance.setHealthy(instance1.isHealthy());
                instance.setLastBeat(instance1.getLastBeat());
            }
            instanceMap.put(instance.getDatumKey(), instance);
        }
        return instanceMap;
    }

    /**
     * 检查服务是否为空。
     *
     * @param service     服务
     * @param namespaceId 命名空间的id
     * @param serviceName 服务名称
     * @throws RcException 自定义的异常
     */
    private void checkServiceIsNull(Service service, String namespaceId, String serviceName) throws RcException {
        if (service == null) {
            throw new RcException("service not found, namespace: " + namespaceId + ", serviceName: " + serviceName);
        }
    }

    /**
     * 根据命名空间的id和服务名称获取服务。
     *
     * @param namespaceId 命名空间的id
     * @param serviceName 服务名称
     * @return
     */
    public Service getService(String namespaceId, String serviceName) {
        Map<String, Service> stringServiceMap = serviceMap.get(namespaceId);
        if (stringServiceMap == null) {
            return null;
        }

        return chooseServiceMap(namespaceId).get(serviceName);
    }

    /**
     * 根据命名空间的id选择服务。
     *
     * @param namespaceId 命名空间的id
     * @return
     */
    public Map<String, Service> chooseServiceMap(String namespaceId) {
        return serviceMap.get(namespaceId);
    }

    /**
     * 创建一个空的服务，
     *
     * @param namespaceId 命名空间的id。
     * @param serviceName 服务名称。
     * @param ephemeral   是否是临时节点，<code>true</code>为临时节点，<code>false</code>为永久节点。
     */
    private void createEmptyService(String namespaceId, String serviceName, boolean ephemeral) {
        createServiceIfAbsent(namespaceId, serviceName);
    }

    /**
     * 创建一个不存在的服务
     *
     * @param namespaceId 命名空间的id
     * @param serviceName 服务名称
     */
    private void createServiceIfAbsent(String namespaceId, String serviceName) {
        Service service = getService(namespaceId, serviceName);
        if (service != null) {
            service.init();
            return;
        }

        System.out.println("创建一个空的服务，命名空间id：" + namespaceId + "，服务名称：" + serviceName);
        service = new Service();
        service.setNamespaceId(namespaceId);
        service.setServiceName(serviceName);
        service.setGroupName(NamingUtils.getGroupName(serviceName));

        putServiceAndInit(service);
    }

    /**
     * 把服务放入到真正的注册表中，并初始化健康检查的任务。
     *
     * @param service 当前服务
     */
    private void putServiceAndInit(Service service) {
        putService(service);
        service = getService(service.getNamespaceId(), service.getServiceName());
        service.init();

        // 监听实例变化
        consistencyService.listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getServiceName(), true), service);
    }

    /**
     * 把服务放到注册表中
     *
     * @param service 当前服务
     */
    private void putService(Service service) {
        if (!serviceMap.containsKey(service.getNamespaceId())) {
            // putIfAbsent与put的区别：第一个有则不处理，返回之前的值，没有就进行添加，第二个不管有没有，都只进行覆盖的处理。
            serviceMap.putIfAbsent(service.getNamespaceId(), new ConcurrentHashMap<>());
        }

        serviceMap.get(service.getNamespaceId()).putIfAbsent(service.getServiceName(), service);
    }
}
